package genericTree;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Tree node data includes parent
 */
public class GenericTreeNode<T extends IncorporateParent<T>> implements Serializable {
    
    private static final long serialVersionUID = -8936185062484573685L;
    
    /** The data. */
    public T data;
    
    /** The children. */
    public List<GenericTreeNode<T>> children;
    
    /** The parent. */
    private GenericTreeNode<T> parent;
    
    public GenericTreeNode() {
        super();
        parent = null;
        children = new ArrayList<GenericTreeNode<T>>();
    }
    
    public GenericTreeNode(T aData) {
        this();
        setData(aData.createNewInstance());
    }
    
    public List<GenericTreeNode<T>> getChildren() {
        return this.children;
    }
    
    public GenericTreeNode<T> getParent() {
        return this.parent;
    }
    
    public List<GenericTreeNode<T>> getAncestors() {
        List<GenericTreeNode<T>> ancestors = new LinkedList<GenericTreeNode<T>>();
        GenericTreeNode<T> p = this.parent;
        while (p != null) {
            ancestors.add(p);
            p = p.parent;
        }
        return ancestors;
    }
    
    public int getNumberOfChildren() {
        return getChildren().size();
    }
    
    public boolean hasChildren() {
        return (getNumberOfChildren() > 0);
    }
    
    public void setChildren(List<GenericTreeNode<T>> aChildren) {
        this.children = aChildren;
    }
    
    public void addChild(GenericTreeNode<T> child) {
        child.parent = this;
        children.add(child);
    }
    
    public void addChildAt(int index, GenericTreeNode<T> child)
            throws IndexOutOfBoundsException {
        child.parent = this;
        children.add(index, child);
    }
    
    public void removeChildren() {
        this.children = new ArrayList<GenericTreeNode<T>>();
    }
    
    public void removeChildAt(int index) throws IndexOutOfBoundsException {
        children.remove(index);
    }
    
    public GenericTreeNode<T> getChildAt(int index)
            throws IndexOutOfBoundsException {
        return children.get(index);
    }
    
    public T getData() {
        return this.data;
    }
    
    public void setData(T tdata) {
        this.data = tdata;
    }
    
    public String toString() {
        StringBuffer rtn = new StringBuffer();
        if (getData() != null) {
        	rtn.append("Node Data");
        	rtn.append('\n');
            rtn.append(getData().toString());
        } else {
            rtn.append("No Node Data");
        }
        
        return rtn.toString();
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof GenericTreeNode)) {
            return false;
        }
        @SuppressWarnings("unchecked")
        GenericTreeNode<T> other = (GenericTreeNode<T>) obj;
        if (data == null) {
            if (other.data != null) {
                return false;
            }
        } else if (!data.equals(other.data)) {
            return false;
        }
        return true;
    }
    
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((data == null) ? 0 : data.hashCode());
        return result;
    }
    
    /**
     * To string verbose.
     *
     * @return the string
     */
    public String toStringVerbose() {
        String stringRepresentation = getData().toString() + "\nchildren[\n";
        int iChild=0;
        final int nChildren = getChildren().size();
        for (GenericTreeNode<T> node : getChildren()) {
            iChild++;
            stringRepresentation += "Child " + iChild + " / "
                    + nChildren + "\n";
            stringRepresentation += node.getData().toString() + "\n";
        }
        
        //Pattern.DOTALL causes ^ and $ to match. Otherwise it won't. It's retarded.
        Pattern pattern = Pattern.compile(", $", Pattern.DOTALL);
        Matcher matcher = pattern.matcher(stringRepresentation);
        
        stringRepresentation = matcher.replaceFirst("");
        stringRepresentation += "]";
        
        return stringRepresentation;
    }
    
    /**
     * Index of.
     *
     * @param aChild is the child data to search for
     * @return the index of the child
     */
    public int indexOf(GenericTreeNode<T> aChild) {
        int index = children.indexOf(aChild);
        return index;
    }
    
    /**
     * Incorporate ancestors.
     *
     * @return the generic tree node
     */
    public T incorporateAncestors() {
    	final boolean debugPrint = false;
    	return incorporateAncestors(debugPrint);
    }
    
    /**
     * Incorporate the parent nodes up to root to obtain a node that
     * incorporates
     * the data from all ancestors.
     *
     * @param debugPrint should debug information be printed
     * @return A node that incorporates data from all ancestors
     */
    public T incorporateAncestors(final boolean debugPrint) {
        if (debugPrint) {
            System.out.printf("\nDebug print for incorporateAncestors\n");
            System.out
            .printf("  Starting at node at depth %d\n", this.getDepth());
        }
        
        /* Debugging null pointer
           Appears to occur on the line
           final GenericTreeNode<T> nodeWithAncestors = new GenericTreeNode<T>(
                this.data.clone());
           The file nnpm.txt had the information
             Problem with null pointer in GenericTreeNode::incorporateAncestors()
             Parent has 3 layers: 2 outputs,  4 outputs,  4 outputs,
             Child has 3 layers: null outputs,  null outputs,  3 outputs,
         */
        
        final GenericTreeNode<T> nodeWithAncestors = new GenericTreeNode<T>(
                this.data);
        if (debugPrint) {
        }
        GenericTreeNode<T> currentParent = this.parent;
        while (currentParent != null) {
//            final T parentsData = currentParent.data;
//            
//            final T childData = nodeWithAncestors.getData();
            T childWithParent=null;
            nodeWithAncestors.setData(childWithParent);
            currentParent = currentParent.parent;
        }
        return nodeWithAncestors.data;
    }
    
    /**
     * Gets the path.
     *
     * @return the path
     */
    public String getPath() {
        String path = null;
        GenericTreeNode<T> currentChild = this;
        GenericTreeNode<T> currentParent = currentChild.parent;
        while (currentParent != null) {
            int parentsChildIndex = currentParent.indexOf(currentChild);
            if (path == null) {
                path = String.format("%d", parentsChildIndex);
            } else {
                path = String.format("%d_%s", parentsChildIndex, path);
            }
            currentChild = currentParent;
            currentParent = currentParent.parent;
        }
        return path;
    }
    
    /**
     * Gets the depth.
     *
     * @return the depth
     */
    public int getDepth() {
        GenericTreeNode<T> currentNode = this;
        int depth = 0;
        while (currentNode.parent != null) {
            currentNode = currentNode.parent;
            depth++;
        }
        return depth;
    }
    
    /**
     * Sets the parent.
     *
     * @param p the new parent
     */
    public void setParent(GenericTreeNode<T> p) {
        parent = p;
    }
}
